package com.studiofortress.sf.structure.intersection; 

import com.studiofortress.sf.structure.Actor;
import com.studiofortress.sf.util.Angle;
import java.awt.geom.Ellipse2D;

/**
 * An EllipseIntersection represents the given Actor as an
 * ellipse or circle when detecting intersections. It also
 * gives some specific optimisations for comparing an
 * EllipseIntersection to another EllipseIntersection.
 * 
 * The width and height of the EllipseIntersection will be
 * equal to the Actors width and height at the moment when
 * the EllipseIntersection was created.
 * 
 * Users should note that EllipseIntersections are compared
 * with other EllipseIntersections for an intersection, if
 * both EllipseIntersections are a circle (the Actor has the
 * same width and height) then the comparison is far faster
 * then if one of them was an ellipse.
 * 
 * @author Joseph Lenton
 * @version 03/08/2008
 */
public class EllipseIntersection extends ShapeIntersection
{
    /**
     * Creates a new EllipseIntersection which represents the given Actor as an ellipse,
     * using that actors current width and height for the width and height of the EllipseIntersection.
     * @param actor The actor this will be used to represent during intersections, cannot be null.
     */
    public EllipseIntersection(Actor actor)
    {
        this(actor, actor.getWidth(), actor.getHeight());
    }
    
    /**
     * Creates the EllipseIntersection as a perfect circle
     * using the given diameter. The getWidth and getHeight methods
     * will return the same diameter value.
     * @param actor The Actor to associate with this intersection. Cannot be null.
     * @param diameter The diameter of this circular EllipseIntersection. Must be greater then 0.
     */
    public EllipseIntersection(Actor actor, int diameter)
    {
        this(actor, diameter, diameter);
    }
    
    /**
     * Creates an elliptical EllipseIntersection using the given width
     * and height. Those values are the width and height across the
     * entire ellipse.
     * @param actor The Actor to associate with this intersection. Cannot be null.
     * @param width The width of this EllipseIntersection. Must be greater then 0.
     * @param height The height of this EllipseIntersection. Must be greater then 0.
     */
    public EllipseIntersection(Actor actor, int width, int height)
    {
        super(actor, new Ellipse2D.Float( -width/2, -height/2, width, height ));
    }
    
    /**
     * An override of isIntersectionToShape to add EllipseIntersection
     * to EllipseIntersetion intersection checking optimisations.
     * If the given ShapeIntersection is not an EllipseIntersection then
     * none of these optimisations are applied and it is the same as
     * calling the isIntersectionToShape in the ShapeIntersection class.
     * @param other The ShapeIntersection to check an intersection against.
     * @return True if this EllipseIntersection intersects with the given ShapeIntersection, false if not.
     */
    @Override
    protected boolean isIntersectionToShape(ShapeIntersection other)
    {
        Actor thisActor = this.getActor();
        Actor otherActor = other.getActor();
        
        /* the use of '>> 1' is instead of 'divide by 2', they perform the same thing */
        if (other instanceof EllipseIntersection) {
            int thisHalfWidth = thisActor.getWidth() >> 1;
            int thisHalfHeight = thisActor.getHeight() >> 1;
            int otherHalfWidth = otherActor.getWidth() >> 1;
            int otherHalfHeight = otherActor.getHeight() >> 1;
            
            int xDiff = (int)Math.abs(otherActor.getX() - thisActor.getX());
            int yDiff = (int)Math.abs(otherActor.getY() - thisActor.getY());
            
            // is it definitely outside the ellipse?
            if (xDiff > thisHalfWidth + otherHalfWidth | yDiff > thisHalfHeight + otherHalfHeight) {
                return false;
            // is it definitely inside the ellipse?
            } else if (xDiff < (thisHalfWidth >> 1) && yDiff < (thisHalfHeight >> 1)) {
                return true;
            /* it's intersection is somewhere between certain success and fail,
             * here we test if both are circles */
            } else if (thisHalfWidth == thisHalfHeight && otherHalfWidth == otherHalfHeight) {
                int hypot = thisHalfWidth+otherHalfWidth;
                // width and height are the same value, they are both the radius
                return xDiff*xDiff + yDiff*yDiff <= hypot*hypot;
            // it's somewhere in between and it's an ellipse, lets do some expensive maths
            } else {
                float angle = Angle.atan2(yDiff, xDiff);
                float hypotSqr = xDiff*xDiff + yDiff*yDiff;
                
                float collideX = (thisHalfWidth + otherHalfWidth)*Angle.cos(angle);
                float collideY = (thisHalfHeight + otherHalfHeight)*Angle.sin(angle);
                float collideHypotSqr = collideX*collideX + collideY*collideY;
                
                return collideHypotSqr >= hypotSqr;
            }
        }
        
        return Intersection.intersectionShapeToShape(thisActor, getShape(), otherActor, other.getShape());
    }
}
